package com.iflytek.jzcpx.procuracy.web.config;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Date;
import java.util.function.BiConsumer;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.ArrayUtils;
import com.iflytek.jzcpx.procuracy.card.config.WsmbbmConfig;
import com.iflytek.jzcpx.procuracy.common.enums.CommonResultEnum;
import com.iflytek.jzcpx.procuracy.common.enums.MetricEnum;
import com.iflytek.jzcpx.procuracy.common.model.HandContResult;
import com.iflytek.jzcpx.procuracy.common.result.Result;
import com.iflytek.jzcpx.procuracy.common.result.WebResult;
import com.iflytek.jzcpx.procuracy.common.util.MetricUtil;
import com.iflytek.jzcpx.procuracy.cont.model.SuccessJsonResult;
import com.iflytek.jzcpx.procuracy.ocr.entity.Metric;
import com.iflytek.jzcpx.procuracy.ocr.entity.swx.OcrWJsbSingleResult;
import com.iflytek.jzcpx.procuracy.ocr.service.MetricService;
import com.iflytek.jzcpx.procuracy.tools.common.PropUtils;
import com.iflytek.jzcpx.procuracy.tools.elle.ElleTextTypeEnum;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

/**
 * 统计指标收集切面
 *
 * @author dgyu
 * @date 2020-09-29 11:35
 */
@Aspect
@Component
public class MetricCollectorAspect {

    private static final Logger logger = LoggerFactory.getLogger(MetricCollectorAspect.class);

    @Autowired
    private MetricService metricService;
    @Autowired
    private WsmbbmConfig wsmbbmConfig;

    @Around("execution(public com.iflytek.jzcpx.procuracy.common.result.Result com.iflytek.jzcpx.procuracy.web.card" +
            ".controller..*.*(..))")
    public Result<?> handleControllerMethod(ProceedingJoinPoint pjp) {
        Metric metric = null;
        try {
            metric = createMetric(pjp);
        }
        catch (Exception e) {
            log(e, "准备Metric异常");
        }

        Result<?> resultEntity = null;
        Throwable exception = null;
        try {
            resultEntity = (Result<?>) pjp.proceed(pjp.getArgs());
        }
        catch (Throwable throwable) {
            exception = throwable;
        }
        if (metric == null) {
            return resultEntity;
        }

        try {
            if (exception != null) {
                handlerException(pjp, metric, exception);
            }
            else {
                String ocrResult = null == resultEntity ||
                                   !resultEntity.isSuccess() ? CommonResultEnum.FAILED.name() :
                        CommonResultEnum.SUCCESS.name();
                stopMetric(metric, ocrResult, resultEntity.getFileId());
            }
            metricService.save(metric);
        }
        catch (Exception e) {
            log(e, "web接口执行完毕, 更新埋点信息异常");
        }
        return resultEntity;
    }

    private void log(Exception e, String msg) {
        if (PropUtils.getBool("metric_exception_detail", false)) {
            logger.warn(msg, e);
        }
        else {
            logger.warn("{}, {}", msg, e.getMessage());
        }
    }

    /**
     * 自动编目请求拦截器
     *
     * @param pjp
     *
     * @return
     */
    @Around("execution(public com.iflytek.jzcpx.procuracy.cont.model.SuccessJsonResult com.iflytek.jzcpx.procuracy" +
            ".web.cont.controller..*.*(..))")
    public SuccessJsonResult<?> handleContControllerMethod(ProceedingJoinPoint pjp) {
        Metric metric = null;
        try {
            metric = createMetric(pjp);
        }
        catch (Exception e) {
            log(e, "准备Metric异常");
        }

        SuccessJsonResult<?> resultEntity = null;
        Throwable exception = null;
        try {
            resultEntity = (SuccessJsonResult<?>) pjp.proceed(pjp.getArgs());
        }
        catch (Throwable throwable) {
            exception = throwable;
        }
        if (metric == null) {
            return resultEntity;
        }

        try {
            if (exception != null) {
                handlerException(pjp, metric, exception);
            }
            else {
                String ocrResult = null == resultEntity || !resultEntity.getCode().equals(
                        "0") ? CommonResultEnum.FAILED.name() : CommonResultEnum.SUCCESS.name();
                stopMetric(metric, ocrResult, null);
            }
            metricService.save(metric);
        }
        catch (Exception e) {
            log(e, "web接口执行完毕, 更新埋点信息异常");
        }
        return resultEntity;
    }

    /**
     * 自动编目请求拦截器
     *
     * @param pjp
     *
     * @return
     */
    @Around("execution(public com.iflytek.jzcpx.procuracy.common.model.HandContResult com.iflytek.jzcpx.procuracy.web" +
            ".cont.controller..*.*(..))")
    public HandContResult handleHandContControllerMethod(ProceedingJoinPoint pjp) {

        Metric metric = null;
        try {
            metric = createMetric(pjp);
        }
        catch (Exception e) {
            log(e, "准备Metric异常");
        }

        HandContResult resultEntity = null;
        Throwable exception = null;
        try {
            resultEntity = (HandContResult) pjp.proceed(pjp.getArgs());
        }
        catch (Throwable throwable) {
            exception = throwable;
        }
        if (metric == null) {
            return resultEntity;
        }

        try {
            if (exception != null) {
                handlerException(pjp, metric, exception);
            }
            else {
                String ocrResult = null == resultEntity || !resultEntity.getCode().equals("0") ||
                                   resultEntity.isSuccess() ? CommonResultEnum.FAILED.name() :
                        CommonResultEnum.SUCCESS.name();
                stopMetric(metric, ocrResult, null);
            }
            metricService.save(metric);
        }
        catch (Exception e) {
            log(e, "web接口执行完毕, 更新埋点信息异常");
        }
        return resultEntity;
    }

    @Around("execution(public com.iflytek.jzcpx.procuracy.ocr.entity.swx.OcrWJsbSingleResult com.iflytek.jzcpx" +
            ".procuracy.web.ocr.controller..*.*(..))")
    public OcrWJsbSingleResult handleOcrSbControllerMethod(ProceedingJoinPoint pjp) {
        Metric metric = null;
        try {
            metric = createMetric(pjp);
        }
        catch (Exception e) {
            log(e, "准备Metric异常");
        }

        OcrWJsbSingleResult resultEntity = null;
        Throwable exception = null;
        try {
            resultEntity = (OcrWJsbSingleResult) pjp.proceed(pjp.getArgs());
        }
        catch (Throwable throwable) {
            exception = throwable;
        }
        if (metric == null) {
            return resultEntity;
        }

        try {
            if (exception != null) {
                handlerException(pjp, metric, exception);
            }
            else {
                String ocrResult =
                        null == resultEntity ? CommonResultEnum.FAILED.name() : CommonResultEnum.SUCCESS.name();
                if (metric.getMrtricType().equalsIgnoreCase(MetricEnum.OCR.name())) {
                    metric.setOcrEndTime(new Date());
                }
                stopMetric(metric, ocrResult, resultEntity.getFileId());
            }

            metricService.save(metric);
        }
        catch (Exception e) {
            log(e, "web接口执行完毕, 更新埋点信息异常");
        }
        return resultEntity;
    }

    @Around("execution(public com.iflytek.jzcpx.procuracy.common.result.WebResult com.iflytek.jzcpx.procuracy.web.ocr" +
            ".controller..*.*(..))")
    public WebResult<?> handleOcrControllerMethod(ProceedingJoinPoint pjp) {
        Metric metric = null;
        try {
            metric = createMetric(pjp);
        }
        catch (Exception e) {
            log(e, "准备Metric异常");
        }

        WebResult<?> resultEntity = null;
        Throwable exception = null;
        try {
            resultEntity = (WebResult<?>) pjp.proceed(pjp.getArgs());
        }
        catch (Throwable throwable) {
            exception = throwable;
        }
        if (metric == null) {
            return resultEntity;
        }

        try {
            if (exception != null) {
                handlerException(pjp, metric, exception);
            }
            else {
                String ocrResult = null == resultEntity ||
                                   !resultEntity.isSuccess() ? CommonResultEnum.FAILED.name() :
                        CommonResultEnum.SUCCESS.name();
                if (MetricEnum.OCR.name().equalsIgnoreCase(metric.getMrtricType())) {
                    metric.setOcrEndTime(new Date());
                }
                stopMetric(metric, ocrResult, resultEntity.getFileId());
            }
            metricService.save(metric);
        }
        catch (Exception e) {
            log(e, "web接口执行完毕, 更新埋点信息异常");
        }
        return resultEntity;
    }

    private void stopMetric(Metric metric, String ocrResult, Long fileId) {
        if (null != metric) {
            Date d = new Date();
            Long endTimeMillis = d.getTime();
            metric.setEndMillis(endTimeMillis);
            metric.setCostTime(endTimeMillis - metric.getStartMillis());
            // 处理完请求，返回内容
            logger.debug("SPEND TIME : {}ms", metric.getCostTime());
            metric.setOcrResult(ocrResult);
            metric.setFileId(fileId);
        }
    }

    public Metric createMetric(ProceedingJoinPoint pjp) {
        // 接收到请求，记录请求内容
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes == null) {
            logger.debug("ServletRequestAttributes为null, metric创建失败");
            return null;
        }

        // 获取方法头 是否有指标收集MetricInterface注解
        MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
        Method method = methodSignature.getMethod();
        MetricInterface annotation = method.getAnnotation(MetricInterface.class);
        if (annotation == null) {
            logger.debug("MetricInterface注解为null, metric创建失败");
            return null;
        }

        int intVal = annotation.intVal();
        String stringVal = annotation.stringVal();
        logger.debug("打印注解上的参数：[intVal={}][stringVal={}]", intVal, stringVal);

        HttpServletRequest request = attributes.getRequest();
        // 记录下请求内容
        logger.debug("{} {} From {}, Handle by {} with ARGS: {}", request.getMethod(), request.getRequestURI(),
                     request.getRemoteAddr(), methodSignature.getDeclaringTypeName() + "." + methodSignature.getName(),
                     Arrays.toString(pjp.getArgs()));

        Metric metric = buildMetric(stringVal, request);

        // @RequestBody 请求类型
        applyFromBody(pjp, request, metric);

        return metric;
    }

    private void applyFromBody(ProceedingJoinPoint pjp, HttpServletRequest request, Metric metric) {
        Object[] parameterValues = pjp.getArgs();
        if (ArrayUtils.isEmpty(parameterValues) || metric == null) {
            return;
        }

        try {
            JSONObject jsonObject = null;
            jsonObject = getRequestBody(parameterValues);
            if (jsonObject == null) {
                return;
            }

            // 文件编号
            applyField(request, metric, jsonObject, "wjbh", Metric::setWjxh);
            // 单位编码
            applyField(request, metric, jsonObject, "dwbm", Metric::setDwbm);
            // 部门受案号
            applyField(request, metric, jsonObject, "bmsah", Metric::setBmsah);
            // 案件类别编码
            applyField(request, metric, jsonObject, "ajlbbm", Metric::setAjlbbm);
            // 卷宗编号
            applyField(request, metric, jsonObject, "jzbh", Metric::setJzbh);
            // 目录编号
            applyField(request, metric, jsonObject, "mlbh", Metric::setMlbh);
            // 标识编号
            applyField(request, metric, jsonObject, "bsbh", Metric::setBsbh);
            // 应用和单位的唯一标识
            String systemid = StringUtils.isNotBlank(request.getHeader("systemid")) ? request.getHeader(
                    "systemid") : request.getParameter("systemid");
            if (StringUtils.isEmpty(systemid) && ArrayUtils.isNotEmpty(parameterValues)) {
                systemid = jsonObject.getString("systemid");
                metric.setSystemId(systemid);
            }
            // 文书类型
            String wslx = request.getParameter("wslx");
            if (StringUtils.isEmpty(wslx) && ArrayUtils.isNotEmpty(parameterValues)) {
                String wsmbbm = jsonObject.getString("wsmbbm");
                ElleTextTypeEnum elleTextTypeEnum = wsmbbmConfig.getElleTextType(wsmbbm);
                metric.setWslx(elleTextTypeEnum.name());
            }
        }
        catch (Exception e) {
            logger.warn("解析RequestBody参数异常, {}", e.getMessage());
        }
    }

    private JSONObject getRequestBody(Object[] parameterValues) {
        JSONObject jsonObject;
        for (Object param : parameterValues) {
            RequestBody annotation = param.getClass().getAnnotation(RequestBody.class);
            if (annotation != null) {
                if (param instanceof String) {
                    jsonObject = JSON.parseObject((String) param);
                }
                else {
                    jsonObject = JSON.parseObject(JSON.toJSONString(param));
                }
                return jsonObject;
            }
        }
        return null;
    }

    private void applyField(HttpServletRequest request, Metric metric, JSONObject jsonObject, String key,
            BiConsumer<Metric, String> consumer) {
        String value = request.getParameter(key);
        if (StringUtils.isEmpty(value)) {
            value = jsonObject.getString(key);
            consumer.accept(metric, value);
        }
    }

    @NotNull
    private Metric buildMetric(String stringVal, HttpServletRequest request) {
        try {
            Metric metric = new Metric();
            String uri = request.getRequestURI();
            MetricEnum metricEnum = MetricEnum.getMetricEnum(uri);
            JSONObject obj = MetricUtil.buildMetricParam(request, metricEnum, uri);
            // 构造指标统计数据
            Metric metricEntity = JSONObject.toJavaObject(obj, Metric.class);
            metric.setCallerIp(metricEntity.getCallerIp());
            metric.setCreateTime(metricEntity.getCreateTime());
            metric.setInterfaceName(metricEntity.getInterfaceName());
            metric.setMrtricType(metricEntity.getMrtricType());
            metric.setServiceIp(metricEntity.getServiceIp());
            metric.setSystemId(metricEntity.getSystemId());
            metric.setFileId(null);
            metric.setWjxh(request.getParameter("wjbm"));
            metric.setStartMillis(metricEntity.getCreateTime().getTime());
            metric.setRemark(stringVal);
            metric.setAjlbbm(metricEntity.getAjlbbm());
            metric.setDwbm(metricEntity.getDwbm());
            metric.setBmsah(metricEntity.getBmsah());
            if (metricEnum.name().equalsIgnoreCase(MetricEnum.OCR.name())) {
                metric.setOcrStartTime(metricEntity.getCreateTime());
            }
            metric.setJzbh(request.getParameter("jzbh"));
            metric.setMlbh(request.getParameter("mlbh"));
            String wslx = request.getParameter("wslx");
            if (StringUtils.isNotBlank(wslx)) {
                ElleTextTypeEnum elleTextTypeEnum = ElleTextTypeEnum.fromWslx(wslx);
                metric.setWslx(elleTextTypeEnum.name());
            }
            return metric;
        }
        catch (Exception e) {
            logger.warn("初始化Metric异常, {}", e.getMessage());
            return null;
        }
    }

    private void handlerException(ProceedingJoinPoint pjp, Metric metric, Throwable e) {
        logger.info("controller方法异常, 方法:{} 参数:{}, {}", pjp.getSignature(), pjp.getArgs(), e.getMessage());
        if (null != metric) {
            Date d = new Date();
            Long endTimeMillis = d.getTime();
            metric.setEndMillis(endTimeMillis);
            metric.setCostTime(endTimeMillis - metric.getStartMillis());
            if (metric.getMrtricType().equalsIgnoreCase(MetricEnum.OCR.name())) {
                metric.setOcrEndTime(d);
            }
            // 处理完请求，返回内容
            logger.debug("SPEND TIME : {}ms", metric.getCostTime());
            metric.setOcrResult(CommonResultEnum.EXCEPTION.name());
        }
    }

}
